#include "fsxsimconnect.h"
#include "fsxsimconnectqtfier.h"
#include <QDebug>
using namespace FsxSimConnect;

//
// Default constructor
//
FSXSimConnectSettings::FSXSimConnectSettings() :
    fsxAircraftRequestRadiusMeter(2000), fsxSimObjectRequestPeriod(SIMCONNECT_PERIOD_SECOND),
    fsxCallbackStatistics(false),
    kmlModelUrl(""), kmlNetworkLinkUpdatePeriodS(30),
    modelMaximumWaypointsNumber(2000), modelWaypointsTimeFrameMs(-1)
{
    // code
}

//
// Constructor
// Maximum 200km, http://msdn.microsoft.com/en-us/library/cc526983.aspx#SimConnect_RequestDataOnSimObjectType
//
FSXSimConnectSettings::FSXSimConnectSettings(DWORD radiusMeter, SIMCONNECT_PERIOD period,
                                             bool callbackStatistics, int maximumWaypoints, int waypointsTimeFrameMs, QString kmlModelUrl, int kmlUpdatePeriodS) :
    fsxAircraftRequestRadiusMeter(radiusMeter), fsxSimObjectRequestPeriod(period), fsxCallbackStatistics(callbackStatistics),
    modelMaximumWaypointsNumber(maximumWaypoints), modelWaypointsTimeFrameMs(waypointsTimeFrameMs),
    kmlModelUrl(kmlModelUrl), kmlNetworkLinkUpdatePeriodS(kmlUpdatePeriodS)
{
    // code
}

//
// Constructor
//
FSXSimConnect::FSXSimConnect(FSXSimConnectSettings settings, QObject* parent) :
    _pHandleSimConnect(nullptr), _initDataDefinitions(false), _initSystemEvents(false), _runMessageLoop(false), _settings(settings),
    _dispatchTimer(nullptr), QObject(parent)
{
    // code goes here
}

//
// Destructor
//
FSXSimConnect::~FSXSimConnect()
{
    FSXSimConnect::disconnect(); // no signals
    FSXSimConnect::stop();
    FSXSimConnect::closeSimConnect();

}

//
// Start fetching the data
//
void FSXSimConnect::start() {

    // get me my id
    DWORD tid = GetCurrentThreadId(); // returns a QHandle: thread()->currentThreadId();

    // entry check
    if (FSXSimConnect::_runMessageLoop) {
        FSXSimConnect::log("SimConnect already running, thread " + QString::number(tid));
        return;
    }

    FSXSimConnect::_runMessageLoop = true;
    FSXSimConnect::_dispatchTimer = new QTimer(this);
    connect(FSXSimConnect::_dispatchTimer, SIGNAL(timeout()), this, SLOT(slotDispatchSimConnect()), Qt::AutoConnection);
    FSXSimConnect::_dispatchTimer->start(100); // ms
}

//
// End fetching the data
//
void FSXSimConnect::stop() {
    FSXSimConnect::_runMessageLoop = false;
    if (FSXSimConnect::_dispatchTimer) {
        FSXSimConnect::_dispatchTimer->stop();
        FSXSimConnect::_dispatchTimer = nullptr;
    }
}

//
// Dispatching, fetching the data from FSX
//
void FSXSimConnect::slotDispatchSimConnect() {
    if (!FSXSimConnect::_runMessageLoop || !FSXSimConnect::_pHandleSimConnect) {
        FSXSimConnect::stop();
        return;
    }

    HRESULT hr = SimConnect_CallDispatch(*FSXSimConnect::_pHandleSimConnect, FSXSimConnect::simConnectDispatch, this);
    // Sleep reduces the number of dispatch calls and hence the CPU load drastically
    // Depending on number of aircrafts in range the message loop might be really flooded
    // with data. By sleep we drop some data.
    if (hr!=S_OK) FSXSimConnect::log("Message loop, return ok not OK", QtDebugMsg);
}

//
// Open connection to SimConnect
//
bool FSXSimConnect::openSimConnect(void) {
    if (FSXSimConnect::_pHandleSimConnect) return true; // already open
    FSXSimConnect::_pHandleSimConnect = new HANDLE();
    bool success;
    if (SUCCEEDED(SimConnect_Open(FSXSimConnect::_pHandleSimConnect, "FSXSimConnect", NULL, 0, 0, 0))) {
        FSXSimConnect::log("SimConnect opened " + Utils::timestamp(false));
        int failures = FSXSimConnect::initDataDefinitions();
        failures += FSXSimConnect::initSystemEvents();
        // requests fill will be triggered on open received
        FSXSimConnect::log("SimConnect init, failures " + QString::number(failures));
        FSXSimConnect::_lastStatisticsLog = QDateTime::currentMSecsSinceEpoch();
        success = (failures == 0);
    } else {
        FSXSimConnect::closeSimConnect();
        FSXSimConnect::log("SimConnect cannot be opened");
        success = false;
    }
    return success;
}

//
// Close connection to SimConnect
//
void FSXSimConnect::closeSimConnect() {
    if (!FSXSimConnect::_pHandleSimConnect) return;
    FSXSimConnect::stop();
    FSXSimConnect::_mutex.lock();
    if (FSXSimConnect::_pHandleSimConnect) {
        SimConnect_Close(*FSXSimConnect::_pHandleSimConnect);
        delete FSXSimConnect::_pHandleSimConnect;
        FSXSimConnect::_pHandleSimConnect = nullptr;
        FSXSimConnect::_runMessageLoop = false;
    }
    FSXSimConnect::_mutex.unlock();
    FSXSimConnect::_initDataDefinitions = false;
    FSXSimConnect::_initSystemEvents = false;
    FSXSimConnect::_objectIdToRequest.clear();
}

//
// Used when FSX shuts down
//
void FSXSimConnect::killSimConnect() {
    if (!FSXSimConnect::_pHandleSimConnect) return;
    FSXSimConnect::_runMessageLoop = false; // make sure timer events no longer get thru
    FSXSimConnect::_mutex.lock();
    FSXSimConnect::stop();
    delete FSXSimConnect::_pHandleSimConnect;
    FSXSimConnect::_pHandleSimConnect = nullptr;
    FSXSimConnect::_mutex.unlock();
    FSXSimConnect::closeSimConnect(); // clean up details
}

//
// Init data definitions
//
int FSXSimConnect::initDataDefinitions() {

    // entry checks
    if (FSXSimConnect::_initDataDefinitions) return 0;
    if (!FSXSimConnect::_pHandleSimConnect)  return 1;

    // init
    FSXSimConnect::_aircrafts.clear();
    int failures = FSXDataDefinitionsHelper::init(*FSXSimConnect::_pHandleSimConnect);
    FSXSimConnect::_initDataDefinitions = true;
    return failures;
}

//
// Init the system events
// http://msdn.microsoft.com/en-us/library/cc526983.aspx#SimConnect_SubscribeToSystemEvent
//
int FSXSimConnect::initSystemEvents() {

    // entry checks
    if (FSXSimConnect::_initSystemEvents) return 0;
    if (!FSXSimConnect::_pHandleSimConnect) return 1;

    // Subscribe to system events notifying the client that objects have been added or removed
    int failures = 0;
    if (S_OK != SimConnect_SubscribeToSystemEvent(*FSXSimConnect::_pHandleSimConnect, EVENT_ADDED_AIRCRAFT, "ObjectAdded")) failures++;
    if (S_OK != SimConnect_SubscribeToSystemEvent(*FSXSimConnect::_pHandleSimConnect, EVENT_REMOVED_AIRCRAFT, "ObjectRemoved")) failures++;
    FSXSimConnect::_initSystemEvents = true;
    return failures;
}

//
// Request data on a FSX SimObject
//
int FSXSimConnect::requestDataOnSimObject(SIMCONNECT_OBJECT_ID objectId) {

    // entry checks
    if (!FSXSimConnect::_initDataDefinitions) return 1;
    if (!FSXSimConnect::_pHandleSimConnect) return 1;
    if (FSXSimConnect::_aircrafts.contains(objectId)) return 0; // already requested

    // requests
    // we need individual IDs for each object, otherwise the request will be overridden
    int failures = 0;
    SIMCONNECT_PERIOD period = FSXSimConnect::_settings.fsxSimObjectRequestPeriod;
    SIMCONNECT_DATA_REQUEST_ID idString   = FSXDataDefinitionsHelper::getRequestId(FSXDataDefinitionsHelper::RequestAircraftStringAndMiscData);
    SIMCONNECT_DATA_REQUEST_ID idPosSpeed = FSXDataDefinitionsHelper::getRequestId(FSXDataDefinitionsHelper::RequestAircraftPositionAndSpeedData);
    SIMCONNECT_DATA_REQUEST_ID idInst     = FSXDataDefinitionsHelper::getRequestId(FSXDataDefinitionsHelper::RequestAircraftInstrumentationData);
    SIMCONNECT_DATA_REQUEST_ID idAvi      = FSXDataDefinitionsHelper::getRequestId(FSXDataDefinitionsHelper::RequestAircraftAvionicsData);

    if (S_OK != SimConnect_RequestDataOnSimObject(*FSXSimConnect::_pHandleSimConnect, idString, FSXDataDefinitionsHelper::DataDefinitionAircraftStringAndMiscData, objectId, period, SIMCONNECT_DATA_REQUEST_FLAG_CHANGED)) failures++;
    if (S_OK != SimConnect_RequestDataOnSimObject(*FSXSimConnect::_pHandleSimConnect, idPosSpeed, FSXDataDefinitionsHelper::DataDefinitionAircraftPositionAndSpeedData, objectId, period, SIMCONNECT_DATA_REQUEST_FLAG_CHANGED)) failures++;
    if (S_OK != SimConnect_RequestDataOnSimObject(*FSXSimConnect::_pHandleSimConnect, idInst, FSXDataDefinitionsHelper::DataDefinitionAircraftInstrumentationData, objectId, period, SIMCONNECT_DATA_REQUEST_FLAG_CHANGED)) failures++;
    if (S_OK != SimConnect_RequestDataOnSimObject(*FSXSimConnect::_pHandleSimConnect, idAvi, FSXDataDefinitionsHelper::DataDefinitionAircraftAvionicsData, objectId, period, SIMCONNECT_DATA_REQUEST_FLAG_CHANGED)) failures++;

    FSXSimConnect::_objectIdToRequest.insertMulti(objectId, idString);
    FSXSimConnect::_objectIdToRequest.insertMulti(objectId, idPosSpeed);
    FSXSimConnect::_objectIdToRequest.insertMulti(objectId, idInst);
    FSXSimConnect::_objectIdToRequest.insertMulti(objectId, idAvi);

    return failures;
}

//
// Unrequest data on a FSX SimObject
//
void FSXSimConnect::unrequestDataOnSimObject(SIMCONNECT_OBJECT_ID objectId) {
    if (!FSXSimConnect::_pHandleSimConnect) return;

    QMap<SIMCONNECT_OBJECT_ID, SIMCONNECT_DATA_REQUEST_ID>::const_iterator i = FSXSimConnect::_objectIdToRequest.find(objectId);
    while (i != FSXSimConnect::_objectIdToRequest.end() && i.key() == objectId) {
        SimConnect_RequestDataOnSimObject(*FSXSimConnect::_pHandleSimConnect, i.value(), NULL, objectId, SIMCONNECT_PERIOD_NEVER);
        ++i;
    }
    FSXSimConnect::_objectIdToRequest.remove(objectId);
}

//
// Request aircrafts around.
//
int FSXSimConnect::requestAircraftData() {
    int failures = 0;
    if (S_OK != SimConnect_RequestDataOnSimObjectType(*FSXSimConnect::_pHandleSimConnect, FSXDataDefinitionsHelper::RequestAircraftMinimal, FSXDataDefinitionsHelper::DataDefinitionAircraftMinimal, FSXSimConnect::_settings.fsxAircraftRequestRadiusMeter, SIMCONNECT_SIMOBJECT_TYPE_AIRCRAFT)) failures++;
    if (S_OK != SimConnect_RequestDataOnSimObjectType(*FSXSimConnect::_pHandleSimConnect, FSXDataDefinitionsHelper::RequestAircraftMinimal, FSXDataDefinitionsHelper::DataDefinitionAircraftMinimal, FSXSimConnect::_settings.fsxAircraftRequestRadiusMeter, SIMCONNECT_SIMOBJECT_TYPE_HELICOPTER)) failures++;
    return failures;
}

//
// Logging
//
void FSXSimConnect::log(const QString &s, QtMsgType msgType) {
    if (s.isEmpty()) return;
    emit signalLogMessage(s, msgType); // send to GUI log facility
    // qDebug() << s;
}

//
// Log the statistics
//
void FSXSimConnect::logStatistics() {
    QMap<QString, int>::const_iterator i = FSXSimConnect::_callbackStatistics.constBegin();
    QString stats = QString("Last stats: " + QString::number(QDateTime::currentMSecsSinceEpoch() - FSXSimConnect::_lastStatisticsLog) + " ms");
    while (i != FSXSimConnect::_callbackStatistics.constEnd()) {
        if (!stats.isEmpty()) stats.append("\n");
        stats.append(i.key());
        stats.append(": ");
        stats.append(QString::number(i.value()));
        ++i;
    }
    FSXSimConnect::log(stats);
    FSXSimConnect::_lastStatisticsLog = QDateTime::currentMSecsSinceEpoch();
}

//
// Increase the statistics counter
//
void FSXSimConnect::increaseStatisticsCount(const QString &key)
{
    int c = FSXSimConnect::_callbackStatistics.value(key, 0);
    c++;
    FSXSimConnect::_callbackStatistics.insert(key, c);
}

//
// Get the aircrafts
//
QList<Aircraft> FSXSimConnect::slotAircrafts()
{
    QList<Aircraft> aircrafts = FSXSimConnect::_aircrafts.values();
    return aircrafts;
}

//
// All Aircrafts as JSON array
//
QString FSXSimConnect::slotAircraftsJsonArray(const JsonParameter &parameter)
{
    QString json = FSXSimConnect::_aircrafts.toJsonArray(0, parameter);
    return json;
}

//
// All Aircrafts as HTML table
//
QString FSXSimConnect::slotAircraftsHtmlTable() {
    QString html = FSXSimConnect::_aircrafts.toHtmlTable(1);
    return html;
}

//
// KML waypoints of given aircraft
//
QString FSXSimConnect::slotAircraftKmlWaypoints(const KmlParameter &parameter) {
    SIMCONNECT_OBJECT_ID aid = parameter.id >=0 ? (SIMCONNECT_OBJECT_ID)parameter.id : (SIMCONNECT_OBJECT_ID) 1;
    Aircraft a = FSXSimConnect::_aircrafts.value(aid);
    if (!a.isValid()) return "";
    return a.toKml(0, parameter);
}

//
// Find aircaft by id
//
Aircraft FSXSimConnect::slotFindAircraftById(const SIMCONNECT_OBJECT_ID id)
{
    Aircraft a = FSXSimConnect::_aircrafts.value(id);
    return a;
}

//
// Settings
//
void FSXSimConnect::slotSetSettings(FSXSimConnectSettings settings)
{
    FSXSimConnect::_settings = settings;
}
